package com.betterjr.common.mapper;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;

import com.betterjr.common.data.SimpleDataEntity;
import com.betterjr.common.exception.BytterException;
import com.betterjr.common.exception.BytterFieldNotFoundException;
import com.betterjr.common.utils.BetterDateUtils;
import com.betterjr.common.utils.BetterStringUtils;
import com.betterjr.common.utils.reflection.ReflectionUtils;

public class BeanMapperHelper {

    private static Map<String, BeanMapperHelper> map = new HashMap();

    private final Map<Method, Object> methodMap = new HashMap();
    public static final Object[] objs = new Object[] {};
    public static final Class[] nullClass = new Class[] {};
    // public static Map<Class, Map<String, Method>> getMethodMap = initMethdMap(true);
    public static Map<Class, Map<String, Method>> setMethodMap = initMethdMap(false);

    private static Map initMethdMap(final boolean anReader) {
        // SaleRequestInfo.class, SaleAccoRequestInfo.class, SaleSimpleRequestObj.class
        final Class[] initMethodClasses = new Class[] {};
        final Map<Class, Map<String, Method>> map = new HashMap();
        Map methodMap;
        for (final Class cc : initMethodClasses) {
            methodMap = ReflectionUtils.findMethodMap(cc, anReader);

            map.put(cc, methodMap);
        }

        return map;
    }

    /**
     * 将申请的Map数据转换为需要的对象
     * 
     * @param anMap
     * @param anObj
     */
    public static void mapToClass(final Map<String, SimpleDataEntity> anMap, final Object anObj) {
        Map<String, Method> map = setMethodMap.get(anObj.getClass());
        if (map == null) {
            map = ReflectionUtils.findMethodMap(anObj.getClass(), false);
            synchronized (setMethodMap) {
                setMethodMap.put(anObj.getClass(), map);
            }
        }
        Method mm;
        Object obj;
        SimpleDataEntity sde;
        for (final Map.Entry<String, SimpleDataEntity> ent : anMap.entrySet()) {
            mm = map.get(ent.getKey());
            if (mm != null) {
                sde = ent.getValue();
                obj = parseSimpleObject(sde.getValue(), sde.getName(), mm.getParameterTypes()[0]);
                try {
                    mm.invoke(anObj, obj);
                }
                catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {}
            }
        }
    }

    public static void invoke(final Object source, final Object destObj, final Map anFieldDefMap) {

    }

    public static void invoke(final Object source, final Object destObj, final String anFieldMap) {
        if (source == null || destObj == null) {
            return;
        }
        final String mapKey = source.getClass().getSimpleName() + "," + anFieldMap + ","
                + destObj.getClass().getSimpleName();
        BeanMapperHelper mapperHelp = map.get(mapKey);
        if (mapperHelp == null) {
            mapperHelp = new BeanMapperHelper();
            mapperHelp.init(source.getClass(), destObj.getClass(), anFieldMap);
            map.put(mapKey, mapperHelp);
        }
        mapperHelp.invokeMethod(source, destObj, false);
    }

    /**
     * 将字符串转换 为简单的对象，包括：日期、数字等
     * 
     * @param tmpStr
     * @param anField
     * @param anPatten
     * @param anClass
     * @return
     */
    public static Object parseSimpleObject(final String tmpStr, final String anPatten, final Class anClass) {
        if (StringUtils.isNotBlank(tmpStr)) {
            if (Date.class.isAssignableFrom(anClass)) {
                try {
                    java.util.Date dd;
                    if (StringUtils.isBlank(anPatten)) {
                        dd = BetterDateUtils.parseDate(tmpStr);
                    } else {
                        dd = DateUtils.parseDate(tmpStr, anPatten);
                    }
                    if (anClass.equals(Date.class)) {

                        return dd;
                    }
                    final Constructor cc = anClass.getConstructor(long.class);
                    return cc.newInstance(dd.getTime());
                }
                catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
                        | IllegalArgumentException | InvocationTargetException | ParseException e) {
                    e.printStackTrace();

                    return null;
                }
            } else if (String.class.isAssignableFrom(anClass)) {

                return tmpStr;
            }
            String mName = "parse" + anClass.getSimpleName();
            Method method;
            try {
                if (anClass == Integer.class) {
                    mName = "parseInt";
                } else if (anClass == BigDecimal.class) {

                    return new BigDecimal(tmpStr);
                }
                method = anClass.getMethod(mName, String.class);
                return method.invoke(null, tmpStr);
            }
            catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
                    | InvocationTargetException e) {
                e.printStackTrace();
                return null;
            }
        } else {
            return null;
        }
    }

    public static Object invokeGetterMethod(final Object obj, final String propertyName) {
        final String getterMethodName = "get" + StringUtils.capitalize(propertyName);
        Method mm;
        try {
            mm = obj.getClass().getMethod(getterMethodName, nullClass);
            return mm.invoke(obj, objs);
        }
        catch (final NoSuchMethodException e) {
            e.printStackTrace();
            throw new BytterFieldNotFoundException(60001, "fieldNot Find", e);
        }
        catch (final SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (final IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (final IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (final InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return null;
    }

    public static Map<String, Object> invokeMap(final Object source, final String anFieldMap) {
        final Map<String, Object> result = new HashMap();
        if (source == null) {

            return result;
        }

        final String mapKey = source.getClass().getSimpleName() + "," + anFieldMap;
        BeanMapperHelper mapperHelp = map.get(mapKey);
        if (mapperHelp == null) {
            mapperHelp = new BeanMapperHelper();
            mapperHelp.init(source.getClass(), Map.class, anFieldMap);
            map.put(mapKey, mapperHelp);
        }
        mapperHelp.invokeMethod(source, result, true);

        return result;
    }

    private void invokeMethod(final Object anSource, final Object anDest, final boolean anMap) {
        Method destM;
        Map workMap = null;
        if (anMap) {
            workMap = (Map) anDest;
        }
        for (final Map.Entry<Method, Object> ent : this.methodMap.entrySet()) {
            Object obj;
            try {
                obj = ent.getKey().invoke(anSource, objs);
                if (anMap) {
                    workMap.put(ent.getValue(), obj);
                } else {
                    destM = (Method) ent.getValue();
                    destM.invoke(anDest, obj);
                }
            }
            catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                e.printStackTrace();
                BytterException.unchecked(e);
            }
        }
    }

    private void init(final Class anSource, final Class anDest, final String anFieldMap) {
        String sName;
        String dName;
        Method sourceMethod;
        String tmpMethodName;
        for (final String tmpStr : StringUtils.split(anFieldMap, ";")) {
            try {
                if (Map.class.isAssignableFrom(anDest)) {
                    tmpMethodName = ReflectionUtils.GETTER_PREFIX + StringUtils.capitalize(StringUtils.trim(tmpStr));
                    sourceMethod = anSource.getMethod(tmpMethodName, new Class[] {});
                    methodMap.put(sourceMethod, StringUtils.trim(tmpStr));
                } else {
                    final String[] subTmpArr = StringUtils.split(tmpStr, ":");
                    sName = StringUtils.trim(subTmpArr[0]);
                    if (subTmpArr.length > 1) {
                        dName = StringUtils.trim(subTmpArr[1]);
                    } else {
                        dName = sName;
                    }

                    if (subTmpArr.length == 2) { // && (sName.equals(dName) == false)
                        tmpMethodName = ReflectionUtils.GETTER_PREFIX + StringUtils.capitalize(sName);
                        sourceMethod = anSource.getMethod(tmpMethodName, new Class[] {});
                        tmpMethodName = ReflectionUtils.SETTER_PREFIX + StringUtils.capitalize(dName);
                        final Method destMethod = anDest.getMethod(tmpMethodName, sourceMethod.getReturnType());
                        methodMap.put(sourceMethod, destMethod);
                    }
                }
            }
            catch (NoSuchMethodException | SecurityException e) {
                System.out.println(e);
                throw BytterException.unchecked(e);
            }
        }
    }

    /**
     * 合并对象的属性，只合并目标中为空的字段； 字符为空或空格算空，数字为空或0算空。
     * 
     * @param anSource
     *            源对象
     * @param anDest
     *            需要合并的对象
     * @return
     */
    public static Object mergeObjectIgnoreEmpty(final Object anSource, final Object anDest) {
        if (anSource == null || anDest == null) {

            return BetterStringUtils.nvlCheck(anSource, anDest);
        }

        final Map<String, Object> sourceMap = BeanMapper.map(anSource, Map.class);
        final Map<String, Object> destMap = BeanMapper.map(anDest, Map.class);
        Object obj;
        for (final Map.Entry<String, Object> ent : sourceMap.entrySet()) {
            obj = mergeObjectValue(ent.getValue(), destMap.get(ent.getKey()));
            destMap.put(ent.getKey(), obj);
        }

        BeanMapper.copy(destMap, anDest);
        return anDest;
    }

    private static Object mergeObjectValue(final Object anSourceValue, final Object anDestValue) {
        if (anSourceValue == null || anDestValue == null) {

            return BetterStringUtils.nvlCheck(anSourceValue, anDestValue);
        }

        // 源数据是空的，返回目标数据
        if (anSourceValue instanceof String) {
            if (BetterStringUtils.isBlank((String) anSourceValue)) {

                return anDestValue;
            }
        }

        // 如果目标数据存在，则返回目标数据，否则返回源数据
        if (anDestValue instanceof Number) {
            final Number nn = (Number) anDestValue;
            if (nn instanceof Long || nn instanceof Integer) {
                final long xx = nn.longValue();
                if (Math.abs(xx) > 0) {

                    return anDestValue;
                }
            } else if (nn instanceof BigDecimal || nn instanceof Double || nn instanceof Float) {
                final double xxx = nn.doubleValue();
                if (Math.abs(xxx) > 0.001) {

                    return anDestValue;
                }
            }
        } else {

            return anDestValue;
        }

        return anSourceValue;
    }
}
